package ctopm

import (
	"github.com/jinzhu/gorm"
	"goim-pro/internal/app/models"
	tbl "goim-pro/internal/db"
	"goim-pro/pkg/logs"
)

type ContactOperationImpl struct {
	models.ContactOperation
	db *gorm.DB
}

type IContactOperationRepo interface {
	CreateContactOperation(newContactOperation *models.ContactOperation) (err error)

	FindOne(condition map[string]interface{}) (contactOperation *models.ContactOperation, err error)
	FindAllByUserId(recipientId string) (contactOperations []models.ContactOperation, err error)
	FindAllByUserIdWithTimeRange(targetId string, startDateTime, endDateTime string) (contactOperations []models.ContactOperation, err error)

	FindOneAndUpdate(condition interface{}, updated interface{}) (contactOperation *models.ContactOperation, err error)
	FindLatestAndUpdateMany(senderId, recipientId string, operationType int32, updated interface{}) (latestOperation *models.ContactOperation, err error)

	UpdateMany(condition, updated map[string]interface{}) (contactOperations []models.ContactOperation, err error)

	DeleteContactOperationById(opsId string, isForce bool) (contactOperation *models.ContactOperation, err error)
	DeleteContactOperationRelation(userId, relationId string, isForce bool) (err error)
}

var logger = logs.GetLogger("ERROR")

func NewContactOperationRepo(db *gorm.DB) IContactOperationRepo {
	return &ContactOperationImpl{
		db: db,
	}
}

func (c *ContactOperationImpl) FindOne(condition map[string]interface{}) (contact *models.ContactOperation, err error) {
	contact = new(models.ContactOperation)
	db := c.db.Preload("Sender").Preload("Recipient").Where(condition).First(contact)
	if db.RecordNotFound() {
		return nil, nil
	}
	if err = db.Error; err != nil {
		logger.Errorf("error happened to query user information: %v", err)
	}
	return
}

func (c *ContactOperationImpl) FindAllByUserId(userId string) (contactOperations []models.ContactOperation, err error) {
	db := c.db.Table(tbl.TableContactOperations).Preload("Sender").Preload("Recipient").Where(
		"(senderId = ? or recipientId = ?)", userId, userId,
	).Order("opsId desc").Find(&contactOperations)
	if db.RecordNotFound() {
		return nil, nil
	}
	if err = db.Error; err != nil {
		return nil, err
	}

	return contactOperations, nil
}

func (c *ContactOperationImpl) FindAllByUserIdWithTimeRange(userId string, startDateTime, endDateTime string) (operations []models.ContactOperation, err error) {
	db := c.db.Table(tbl.TableContactOperations).Preload("Sender").Preload("Recipient").Where(
		"senderId = ? and createdAt >= ? and createdAt < ?", userId, startDateTime, endDateTime,
	).Order("opsId desc").Find(&operations)
	if db.RecordNotFound() {
		return nil, nil
	}
	if err = db.Error; err != nil {
		return nil, err
	}

	return operations, nil
}

func (c *ContactOperationImpl) CreateContactOperation(newContactOperation *models.ContactOperation) (err error) {
	db := c.db.Create(newContactOperation)
	if db.Error != nil {
		err = db.Error
		logger.Errorf("error happened to create new contact operation error: %s", err.Error())
	}
	return
}

func (c *ContactOperationImpl) FindOneAndUpdate(condition interface{}, updated interface{}) (contactOperation *models.ContactOperation, err error) {
	contactOperation = new(models.ContactOperation)
	db := c.db.Table(tbl.TableContactOperations).Where(condition).Update(updated).First(contactOperation)
	if err = db.Error; err != nil {
		logger.Errorf("error happened to update contact operation info: %v", err)
		return nil, err
	}
	return contactOperation, nil
}

func (c *ContactOperationImpl) FindLatestAndUpdateMany(senderId, recipientId string, operationType int32, updated interface{}) (latestOperation *models.ContactOperation, err error) {
	latestOperation = new(models.ContactOperation)
	db := c.db.Table(tbl.TableContactOperations).Where(
		"senderId = ? and recipientId = ? and optType = ? and status = 'PENDING'", senderId, recipientId, operationType,
	).Last(latestOperation).Update(updated)
	if err = db.Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, nil
		}
		logger.Errorf("error happened to update contact operation info: %v", err)
		return nil, err
	}
	return latestOperation, nil
}

func (c *ContactOperationImpl) UpdateMany(condition, updated map[string]interface{}) (contactOperations []models.ContactOperation, err error) {
	db := c.db.Table(tbl.TableContactOperations).Where(condition).Update(updated).Find(&contactOperations)
	if err = db.Error; err != nil {
		logger.Errorf("error happened to update contact operation info: %v", err)
		return nil, err
	}
	return contactOperations, nil
}

func (c *ContactOperationImpl) DeleteContactOperationById(opsId string, isForce bool) (contactOperation *models.ContactOperation, err error) {
	db := c.db
	if isForce {
		db = c.db.Unscoped()
	}
	db = db.Delete(&models.ContactOperation{}, "opsId = ?", opsId)
	if db.RecordNotFound() {
		logger.Warningln("remove contact operation fail, opsId not found")
	} else if db.Error != nil {
		logger.Errorf("error happened to remove contact operation: %v", db.Error)
		err = db.Error
	}
	return
}

func (c *ContactOperationImpl) DeleteContactOperationRelation(userId, relationId string, isForce bool) (err error) {
	db := c.db
	if isForce {
		db = c.db.Unscoped()
	}
	db = db.Delete(&models.ContactOperation{}, "(relationId = ?) and (senderId = ? or recipientId = ?)", relationId, userId, userId)
	if db.RecordNotFound() {
		logger.Warningln("remove contact operation fail, opsId not found")
	} else if db.Error != nil {
		logger.Errorf("error happened to remove contact operation: %v", db.Error)
		err = db.Error
	}
	return
}
